package modeller.databasedesignmodel.servicedemand.transaction.sqlquery.demandFetcher;

import exceptions.NoIndexOnAttributesException;
import modeller.databasedesignmodel.Attribute;
import modeller.databasedesignmodel.DatabaseDesign;
import modeller.databasedesignmodel.query.*;
import modeller.databasedesignmodel.relation.Relation;
import modeller.databasedesignmodel.relation.index.AbstractTableIndex;
import modeller.databasedesignmodel.servicedemand.IServiceDemand;
import modeller.databasedesignmodel.servicedemand.transaction.sqlquery.SQLQueryServiceDemand;
import modeller.databasedesignmodel.transaction.SQLQuery;
import queped.NeptuneHelperFunctions;

import java.util.HashSet;

/**
 * Created by:  Jason Ye
 * Date:        23/03/2012
 * Time:        09:20
 */
public class ClusteredTreeIndexCalculatorVisitor extends AbstractIndexedSQLQueryCalculatorVisitor {

    public ClusteredTreeIndexCalculatorVisitor(DatabaseDesign design) {
        super(design);
    }

    @Override
    public IServiceDemand calculate(SequentialScanQuery query) {

        // BD
        Relation relation = query.getRelation().first;
        return new SQLQueryServiceDemand(
                relation.getBNumDbPages()* design.getDAverageIOTime(),
                query.getRelation().getFirst()
        );

    }

    @Override
    public IServiceDemand calculate(EqualitySearchQuery query) {

        // D * logF(B)

        //assume one relation per query, and required relation is at front of list
        Object relation = query.getRelation().first;
        double D = design.getDAverageIOTime();

        try {
            return new SQLQueryServiceDemand(
                    D * getLogBaseFofB(query),
                    query.getRelation().getFirst()
            );
        } catch (NoIndexOnAttributesException e) {
            return defaultCalculator.calculate(query);
        }
    }

    @Override
    public IServiceDemand calculate(EqualityWithRangeSearchQuery query) {

        //assume one relation per query, and required relation is at front of list
        double logFB = 0;
        try {
            logFB = getLogBaseFofB(query);
        } catch (NoIndexOnAttributesException e) {
            return defaultCalculator.calculate(query);
        }

        // D(logF(B)+#matching pages )
        Relation relation = query.getRelation().first;
        double D = design.getDAverageIOTime();

        int numMatchingRows = query.getNumMatchingRows();
        double recordsPerPage = Math.ceil(design.getDBPageSize()/(double)relation.getExpectedRowSize());
        double numMatchingPages =  Math.ceil(((double) numMatchingRows ) / recordsPerPage);

        return new SQLQueryServiceDemand(
                D * (logFB + numMatchingPages),
                query.getRelation().getFirst()
        );
    }

    @Override
    public IServiceDemand calculate(InsertQuery query) {
        // Search + D = D * logF(B) + D = D *( 1 + logF(B))

        //assume one relation per query, and required relation is at front of list
        try {
            return getDTimes1PlusLogFRB(query);
        } catch (NoIndexOnAttributesException e) {
            return defaultCalculator.calculate(query);
        }

    }

    @Override
    public IServiceDemand calculate(UpdateDeleteQuery query) {

        // Search + D = D * logF(B) + D = D *( 1 + logF(B))
        //assume one relation per query, and required relation is at front of list
        try {
            return getDTimes1PlusLogFRB(query);
        } catch (NoIndexOnAttributesException e) {
            return defaultCalculator.calculate(query);
        }

    }

    private double getLogBaseFofB(SQLQuery query) throws NoIndexOnAttributesException {



        Relation relation = query.getRelation().first;
        HashSet<Attribute> attributeHashSet = query.getRelation().second;

        AbstractTableIndex tableIndex = null;

        tableIndex = relation.getIndexWithAttributes(attributeHashSet);

        double B = relation.getBNumDbPages();
        // calculating F requires index and fill factor
        double F = relation.getFIndexTreeFanout(tableIndex, design.getDBFillFactor());

        return NeptuneHelperFunctions.logBaseArbitrary(F, B);



    }

    private SQLQueryServiceDemand getDTimes1PlusLogFRB(SQLQuery query) throws NoIndexOnAttributesException {

        //assume one relation per query, and required relation is at front of list
        double D = design.getDAverageIOTime();

        return new SQLQueryServiceDemand(
                D * (1+ getLogBaseFofB(query)),
                query.getRelation().getFirst()
        );

    }


}